﻿using AiMoWindows.Localization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace AiMoWindows.GHAiMo
{
    static class GH_SyntaxAnalyzer
    {
        static readonly string[] methodNames = new string[] { "GetTree", "GetList", "GetItem", "SetTree", "SetList", "SetItem" };
        public static GH_ParamSyntaxNodeInfo[] FindMethodCalls(SyntaxNode root, out string printInfo, out bool canSendToGH)
        {
            Dictionary<string, GH_ParamSyntaxNodeInfo> methodCalls = new Dictionary<string, GH_ParamSyntaxNodeInfo>();
            printInfo = "";
            canSendToGH = true;

            var invocationExpressions = root.DescendantNodes().OfType<InvocationExpressionSyntax>();
            int index = 0;
            foreach (var invocation in invocationExpressions)
            {
                string methodName = "";
                string paramName = "";
                string typeName = "";
                if (invocation.Expression is GenericNameSyntax genericName)
                {
                    methodName = genericName.Identifier.Text;
                    typeName = genericName.TypeArgumentList.Arguments.FirstOrDefault()?.ToString() ?? "";
                }
                else if (invocation.Expression is IdentifierNameSyntax identifierName)
                {
                    methodName = identifierName.Identifier.Text;
                }
                if (methodNames.Contains(methodName))
                {
                    var paramNameArgument = invocation.ArgumentList.Arguments[0];
                    if (paramNameArgument.Expression is LiteralExpressionSyntax paramNameLiteral)
                    {
                        paramName = paramNameLiteral.Token.ValueText;
                    }
                    else
                    {
                        LinePosition linePosStart = invocation.GetLocation().GetLineSpan().Span.Start;
                        printInfo += $"\n{Local.Get("Must use const string as paramName:")}\n    {invocation}  [Ln: {linePosStart.Line + 1}]";
                        canSendToGH = false;
                        continue;
                    }

                    if (string.IsNullOrEmpty(typeName))
                    {
                        ExpressionSyntax? secondArgument = invocation.ArgumentList.Arguments.ElementAtOrDefault(1)?.Expression;
                        if (secondArgument is DeclarationExpressionSyntax outExpression)
                        {
                            if (outExpression.Type is GenericNameSyntax outGenericType)
                            {
                                //GetTree("name1", out DataTree<int> tree1)
                                typeName = outGenericType.TypeArgumentList.Arguments.FirstOrDefault()?.ToString() ?? "";
                            }
                            else if (outExpression.Type is ArrayTypeSyntax outArrayType)
                            {
                                //GetList("name2", out string[] list2)
                                typeName = outArrayType.ElementType.ToString();
                            }
                            else
                            {
                                typeName = outExpression.Type.ToString();
                            }
                        }
                        else if (secondArgument != null)
                        {
                            if (secondArgument is LiteralExpressionSyntax literalExpressionSyntax)
                            {
                                switch (literalExpressionSyntax.Kind())
                                {
                                    case SyntaxKind.NumericLiteralExpression:
                                        typeName = "double";
                                        break;
                                    default:
                                        typeName = literalExpressionSyntax.Kind().ToString();
                                        break;
                                }
                            }
                            else
                            {
                                typeName = secondArgument.ToString();
                                LinePosition linePosStart = invocation.GetLocation().GetLineSpan().Span.Start;
                                string tempInfo = string.Format(Local.Get("The param type in not clear, you can use {0}<T> to assign a specific type."), methodName);
                                printInfo += $"\n{tempInfo}\n    {invocation}  [Ln: {linePosStart.Line + 1}]";
                            }
                        }
                    }
                    GH_ParamSyntaxNodeInfo info = new GH_ParamSyntaxNodeInfo(
                        index++,
                        methodName,
                        paramName,
                        typeName
                    );
                    if (methodCalls.ContainsKey(info.UniqueName))
                    {
                        LinePosition linePosStart = invocation.GetLocation().GetLineSpan().Span.Start;
                        printInfo += $"\n{Local.Get("Cannot set same paramName with different data type")}\n    {invocation}  [Ln: {linePosStart.Line + 1}]";
                        canSendToGH = false;
                    }
                    else
                    {
                        methodCalls.Add(info.UniqueName, info);
                    }
                }
            }

            GH_ParamSyntaxNodeInfo[] resultInfos = methodCalls.Values.OrderBy(x => x.Index).ToArray();

            if (resultInfos.Length == 0)
            {
                printInfo += "\n" + Local.Get("Warning: you have not set any Input or Output Param of this Grasshopper Component.");
            }
            else
            {
#if DEBUG
                foreach (GH_ParamSyntaxNodeInfo info in resultInfos)
                {
                    printInfo += "\n" + Local.Get("Readed Param:") + $" {info.MethodName}<{info.TypeName}>(\"{info.ParamName}\" ... ";
                }
#endif
            }
            return resultInfos;
        }

    }
}

